﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Globalization;
using System.ComponentModel;

namespace Toolkit.Web.ExtensionMethods
{
    public static class StringExtensions
    {
        /// <summary>
        /// Determines whether this instance and another specified System.String object have the same value.
        /// </summary>
        /// <param name="instance"></param>
        /// <param name="comparing"></param>
        /// <returns></returns>
        public static bool IsCaseInsensitiveEqual(this string instance, string comparing)
        {
            return string.Equals(instance, comparing, StringComparison.OrdinalIgnoreCase);
        }

        /// <summary>
        /// Tries to convert a given string to a given enum type. If unable to convert, the given default value is returned.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="instance"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        public static T ToEnum<T>(this string instance, T defaultValue) where T : struct
        {
            T convertedValue = defaultValue;

            if (!string.IsNullOrEmpty(instance))
            {
                try
                {
                    convertedValue = (T)Enum.Parse(typeof(T), instance.Trim(), true);
                }
                catch (ArgumentException)
                {
                }
            }

            return convertedValue;
        }

        /// <summary>
        /// Formats a North American phone number into a common format.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="defaultAreaCode"></param>
        /// <returns></returns>
        public static string FormatAsNorthAmericanPhoneNumber(this string value, string defaultAreaCode)
        {
            return FormatAsNorthAmericanPhoneNumber(value, defaultAreaCode, System.Threading.Thread.CurrentThread.CurrentCulture);
        }

        /// <summary>
        /// Formats a North American phone number into a common format.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="defaultAreaCode"></param>
        /// <param name="culture"></param>
        /// <returns></returns>
        public static string FormatAsNorthAmericanPhoneNumber(this string value, string defaultAreaCode, CultureInfo culture)
        {
            string countryCode = string.Empty;
            string phoneNumber = string.Empty;
            string extensionNumber = string.Empty;

            // Strip out all non-numeric characters.
            if (!string.IsNullOrEmpty(value))
            {
                value = Regex.Replace(value, "[^.0-9]", "");
            }

            // Remove country code if phone number length is greater than 10 characters and starts with a 1 or 0. 
            // No North American area code starts with a 1 or 0 (they begin at 200). North American country codes
            // will either be 1 or 001. Country exit codes should not be stored.
            // Note: If the length is not greater than 10 characters, we do not want to remove anything. This is
            // most likely due to an invalid phone number given, but that is a validation concern.
            if (!string.IsNullOrEmpty(value) && value.Length > 10 && (value.Substring(0, 1) == "1" || value.Substring(0, 1) == "0"))
            {
                // Remove all leading 0's.
                while (!string.IsNullOrEmpty(value) && value.StartsWith("0"))
                {
                    value = value.Substring(1);
                }

                if (!string.IsNullOrEmpty(value) && value.Length > 10 && value.StartsWith("1"))
                {
                    // Remove country code.
                    value = value.Substring(1);
                }
            }

            if (string.IsNullOrEmpty(value))
            {
                return phoneNumber;
            }

            // If value length is still greater than 10, remove and store the overflow values into a string to use as an extension number.
            if (value.Length > 10)
            {
                extensionNumber = string.Format(" x{0}", value.Substring(10));
                value = value.Substring(0, 10);
            }

            // Note: Must not convert string to integer for regex parsing here to allow for
            // numbers that start with a leading 0.

            // Get formatted phone number without extension.
            if (value.Length < 7)
            {
                phoneNumber = value;
            }
            else if (value.Length == 7 && !string.IsNullOrEmpty(defaultAreaCode))
            {
                phoneNumber = string.Format("({0}) {1}-{2}", defaultAreaCode, value.Substring(0, 3), value.Substring(3));
            }
            else if (value.Length < 10)
            {
                phoneNumber = value;
            }
            else if (value.Length == 10)
            {
                phoneNumber = string.Format("({0}) {1}-{2}", value.Substring(0, 3), value.Substring(3, 3), value.Substring(6));
            }

            if (culture != null)
            {
                // Get current user's culture name.
                string currentCultureName = culture.Name;

                // If current user culture is not a known North American culture, include country code in formatted phone number dislay.
                if (currentCultureName != "en-US" && currentCultureName != "en-CA" && currentCultureName != "fr-CA")
                {
                    countryCode = "1 ";
                }
            }

            // Combine formatted phone number with formatted extension (if one exists).
            return string.Format("{0}{1}{2}", countryCode, phoneNumber, extensionNumber);
        }

        /// <summary>
        /// Remove all html tags within a string.
        /// Note: This fails on the case where there is an attribute on an html tag with a ">" sign within its value.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static string RemoveHtmlTags(this string value)
        {
            return ReplaceHtmlTags(value, null);
        }

        /// <summary>
        /// Replace all html tags (not defined in the given whitelist) within a string with a given value (defaults replacement text to empty string).
        /// Note: This fails on the case where there is an attribute on an html tag with a ">" sign within its value.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="replacement"></param>
        /// <param name="allowed"></param>
        /// <returns></returns>
        public static string ReplaceHtmlTags(this string value, string replacement, params string[] allowed)
        {
            if (string.IsNullOrEmpty(value))
            {
                return value;
            }

            if (string.IsNullOrEmpty(replacement))
            {
                replacement = string.Empty;
            }

            if (allowed != null && allowed.Length > 0)
            {
                return Regex.Replace(value, string.Format(@"<\/*?(?![^>]*?\b(?:{0})\b)[^>]*?>", string.Join("|", allowed)), replacement);
            }
            else
            {
                return Regex.Replace(value, @"<[^>]*>", replacement);
            }
        }

        /// <summary>
        /// Converts a string to any known value type.
        /// </summary>
        /// <param name="value">String to convert.</param>
        /// <param name="type">Type to convert to.</param>
        /// <returns>Converted value (must cast object to desired type outside of this function).</returns>
        public static object Convert(this string value, Type type)
        {
            return Convert(value, type, CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Converts a string to any known value type.
        /// </summary>
        /// <param name="value">String to convert.</param>
        /// <param name="type">Type to convert to.</param>
        /// <param name="provider">Culture specific format provider to use in conversion (defaults to current culture).</param>
        /// <returns>Converted value (must cast object to desired type outside of this function).</returns>
        public static object Convert(this string value, Type type, IFormatProvider provider)
        {
            if (type == typeof(string))
            {
                return value;
            }
            
            if (type == typeof(bool))
            {
                return bool.Parse(value);
            }
            else if (type == typeof(byte))
            {
                return byte.Parse(value, provider);
            }
            else if (type == typeof(sbyte))
            {
                return sbyte.Parse(value, provider);
            }
            else if (type == typeof(char))
            {
                return char.Parse(value);
            }
            else if (type == typeof(DateTime))
            {
                return DateTime.Parse(value, provider);
            }
            else if (type == typeof(DateTimeOffset))
            {
                return DateTimeOffset.Parse(value, provider);
            }
            else if (type == typeof(TimeSpan))
            {
                return TimeSpan.Parse(value, provider);
            }
            else if (type == typeof(decimal))
            {
                return decimal.Parse(value, provider);
            }
            else if (type == typeof(double))
            {
                return double.Parse(value, provider);
            }
            else if (type == typeof(short))
            {
                return short.Parse(value, provider);
            }
            else if (type == typeof(ushort))
            {
                return ushort.Parse(value, provider);
            }
            else if (type == typeof(int))
            {
                return int.Parse(value, provider);
            }
            else if (type == typeof(uint))
            {
                return uint.Parse(value, provider);
            }
            else if (type == typeof(Int16))
            {
                return Int16.Parse(value, provider);
            }
            else if (type == typeof(Int32))
            {
                return Int32.Parse(value, provider);
            }
            else if (type == typeof(Int64))
            {
                return Int64.Parse(value, provider);
            }
            else if (type == typeof(UInt16))
            {
                return UInt16.Parse(value, provider);
            }
            else if (type == typeof(UInt32))
            {
                return UInt32.Parse(value, provider);
            }
            else if (type == typeof(UInt64))
            {
                return UInt64.Parse(value, provider);
            }
            else if (type == typeof(long))
            {
                return long.Parse(value, provider);
            }
            else if (type == typeof(ulong))
            {
                return ulong.Parse(value, provider);
            }
            else if (type == typeof(sbyte))
            {
                return sbyte.Parse(value, provider);
            }
            else if (type == typeof(float))
            {
                return float.Parse(value, provider);
            }
            else if (type == typeof(Single))
            {
                return Single.Parse(value, provider);
            }
            else if (type == typeof(Guid))
            {
                return Guid.Parse(value);
            }

            TypeConverter converter = TypeDescriptor.GetConverter(type);
            return converter.ConvertFromString(value);
        }
        
        /// <summary>
        /// Converts a string to any known value type.
        /// </summary>
        /// <typeparam name="T">Value type to convert to.</typeparam>
        /// <param name="value">String to convert.</param>
        /// <param name="defaultValue">Optional default value to return of conversion fails.</param>
        /// <returns>Converted value or default value if conversion fails.</returns>
        public static T Convert<T>(this string value, Nullable<T> defaultValue) where T : struct
        {
            try
            {
                return (T)Convert(value, typeof(T));
            }
            catch (Exception ex)
            {
                if (defaultValue.HasValue) return defaultValue.Value;
                throw ex;
            }
        }

        /// <summary>
        /// Converts a string to any known value type.
        /// </summary>
        /// <typeparam name="T">Value type to convert to.</typeparam>
        /// <param name="value">String to convert.</param>
        /// <param name="defaultValue">Optional default value to return of conversion fails.</param>
        /// <returns>Converted value or default value if conversion fails.</returns>
        public static Nullable<T> ConvertNullable<T>(this string value, Nullable<T> defaultValue) where T : struct
        {
            try
            {
                return (T)Convert(value, typeof(T));
            }
            catch (Exception)
            {
                return defaultValue;
            }
        }
    }
}
